﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LibBinary.DataSource
{
    public class TextFileInputSource
    {
        private TextReader reader;

        public TextFileInputSource(TextReader reader)
        {
            this.reader = reader;
        }

        public int ReadInt32()
        {
            string word = InternalReadWord();
            int ret;
            if (!Int32.TryParse(word, out ret))
            {
                throw new ReadingFormatException("Invalid number.");
            }
            return ret;
        }

        public short ReadInt16()
        {
            string word = InternalReadWord();
            short ret;
            if (!Int16.TryParse(word, out ret))
            {
                throw new ReadingFormatException("Invalid number.");
            }
            return ret;
        }

        public byte ReadInt8()
        {
            string word = InternalReadWord();
            byte ret;
            if (!Byte.TryParse(word, out ret))
            {
                throw new ReadingFormatException("Invalid number.");
            }
            return ret;
        }

        public bool ReadBool()
        {
            string word = InternalReadWord();
            bool ret;
            if (!Boolean.TryParse(word, out ret))
            {
                throw new ReadingFormatException("Invalid boolean.");
            }
            return ret;
        }

        public string ReadString()
        {
            return ReadQuotedString();
        }

        public void SkipChar(char c)
        {
            SkipSpaces();
            if (reader.Read() != (int)c)
            {
                throw new ReadingFormatException("Expect char '" + c + "'.");
            }
        }

        public bool TrySkipChar(char c)
        {
            SkipSpaces();
            if (reader.Peek() != (int)c)
            {
                return false;
            }
            reader.Read();
            return true;
        }

        public string ReadWord()
        {
            return InternalReadWord();
        }

        private string ReadQuotedString()
        {
            SkipSpaces();

            if (reader.Read() != (int)'"')
            {
                throw new ReadingFormatException("Invalid string.");
            }

            sb.Clear();
            while (true)
            {
                int ic = reader.Read();
                if (ic == -1)
                {
                    throw new ReadingFormatException("Incomplete string.");
                }
                char c = (char)ic;
                if (c == '"')
                {
                    break;
                }

                if (c == '\\')
                {
                    switch (reader.Read())
                    {
                        case (int)'"':
                            sb.Append('"');
                            break;
                        case (int)'\\':
                            sb.Append('\\');
                            break;
                        default:
                            throw new ReadingFormatException("Unknown escape character.");
                    }
                }
                else
                {
                    sb.Append(c);
                }
            }

            return sb.ToString();
        }

        private readonly StringBuilder sb = new StringBuilder();

        private void SkipSpaces()
        {
            while (true)
            {
                int ic = reader.Peek();
                if (ic == -1)
                {
                    break;
                }
                char c = (char)ic;
                if (!IsSpaceChar(c))
                {
                    break;
                }
                reader.Read();
            }
        }

        private bool IsSpaceChar(char c)
        {
            return c == ' ' || c == '\t' || c == '\n' || c == '\r';
        }

        private string InternalReadWord()
        {
            SkipSpaces();

            sb.Clear();
            while (true)
            {
                int ic = reader.Peek();
                if (ic == -1)
                {
                    break;
                }
                char c = (char)ic;
                if (!IsWordChar(c))
                {
                    break;
                }
                reader.Read();
                sb.Append(c);
            }
            return sb.ToString();
        }

        private bool IsWordChar(char c)
        {
            return (c >= 'a' && c <= 'z') ||
                (c >= 'A' && c <= 'Z') ||
                (c >= '0' && c <= '9') ||
                c == '_' || //other chars in str
                c == '.' || c == '+' || c == '-'; //also allow number characters
        }
    }
}
